#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<string.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<unistd.h>


using namespace std;


const int basesize = 1024;
const int argvnum = 64;
const int envnum = 64;

char* gargv[argvnum];          // 模拟main函数参数中的那个数组 里面存放的是拆开后的每条命令
int gargc = 0;                 // 模拟main函数参数里的argc 计数

// 全局的shell工作路径
char pwd[basesize];

char pwdenv[basesize];

// 我的系统的环境变量
char *genv[envnum];

// 全局的变量
int lastcode = 0;

string GetUserName()
{
    string name = getenv("USER");
    return name.empty()?"None":name;
}


string GetHostName()
{
    string hostname = getenv("HOSTNAME");
    return hostname.empty()?"None":hostname;
}


string GetPwd()
{
    if(nullptr == getcwd(pwd,sizeof(pwd))) 
        return "None";

    snprintf(pwdenv, sizeof(pwdenv),"PWD=%s", pwd);
    putenv(pwdenv);
    return pwd;

    //string pwd = getenv("PWD");
    //return pwd.empty()?"None":pwd;
}

string LastDir()
{
    string curr = GetPwd();
    if(curr == "/" || curr == "None") return curr;
    size_t pos = curr.rfind("/");
    if(pos == std::string::npos) return curr;
    return curr.substr(pos+1);
}


string MalkCommandLine()
{
    char commend_line[basesize];
    snprintf(commend_line,basesize,"[%s@%s %s]#",GetUserName().c_str(),GetHostName().c_str(),LastDir().c_str());
    return commend_line;
}


void PrintCommandLine() // 1. 命令行提示符
{
    printf("%s",MalkCommandLine().c_str());
    fflush(stdout);
}


bool GetCommandLine(char command_buffer[], int size)
{
    char* result = fgets(command_buffer,size,stdin);
    if(!result)
    {
        return false;
    }

    command_buffer[strlen(command_buffer)-1]=0; // 消除命令行最后一个\n
    if(strlen(command_buffer)==0) return false;
    
    return true;

}


void ParseCommandLine(char command_buffer[], int len) // 3. 分析命令
{
   (void)len;
    memset(gargv,0,sizeof(gargv));    //  重新初始化全局变量
    gargc = 0;                        // 

    const char* sep = " ";             // 代表把命令之间的空格删掉
    gargv[gargc++] = strtok(command_buffer,sep);
    while((bool)(gargv[gargc++] = strtok(nullptr,sep)));
    gargc--;
}


void AddEnv(const char* item)
{
    int index = 0;
    while(genv[index])
    {
        index++;
    }

    genv[index] = (char*)malloc(strlen(item)+1);
    strncpy(genv[index],item,strlen(item)+1);
    genv[++index] = nullptr;
}


bool CheckAndExecBuiltCommand()
{
    if(strcmp(gargv[0],"cd")==0)
    {
        if(gargc == 2)
        {
            chdir(gargv[1]);
            lastcode = 0;
        }
        else
        {
            lastcode = 1;
        }
        return true;
    }
    else if(strcmp(gargv[0], "export") == 0)
    {
        // export也是内建命令
        if(gargc == 2)
        {
            AddEnv(gargv[1]);
            lastcode = 0;
        }
        else
        {
            lastcode = 2;
        }
        return true;
    }
    else if(strcmp(gargv[0], "env") == 0)
    {
        for(int i = 0; genv[i]; i++)
        {
            printf("%s\n", genv[i]);
        }
        lastcode = 0;
        return true;
    }
    else if(strcmp(gargv[0], "echo") == 0)
    {
        if(gargc == 2)
        {
            // echo $?
            // echo $PATH
            // echo hello
            if(gargv[1][0] == '$')
            {
                if(gargv[1][1] == '?')
                {
                    printf("%d\n", lastcode);
                    lastcode = 0;
                }
            }
            else
            {
                printf("%s\n", gargv[1]);
                lastcode = 0;
            }
        }
        else
        {
            lastcode = 3;
        }
        return true;
    }
    return false;
}

bool ExecuteCommand()   // 4. 执行命令
{
    pid_t id = fork();
    if(id<0)
    {
        return false;
    }

    if(id == 0)
    {
        // 执行命令
        execvpe(gargv[0],gargv,genv);

        // 退出
        exit(1);
    }

    int status = 0;
    pid_t rid = waitpid(id,&status,0);
    if(rid > 0)
    {
        if(WIFEXITED(status))
        {
            lastcode = WEXITSTATUS(status);
        }
        else
        {
            lastcode = 100;
        }
        
        return true;
    }
    return false;
}


// 作为一个shell，获取环境变量应该从系统的配置来
// 但做不到，所以就直接从父shell中获取环境变量
void InitEnv()
{
    extern char** environ;
    int index = 0;
    while(environ[index])
    {
        genv[index] = (char*)malloc(strlen(environ[index]+1));
        strncpy(genv[index],environ[index],strlen(environ[index]+1));
        index++;
    }

    genv[index] = nullptr;
}


int main()
{
    InitEnv();
    char command_buffer[basesize]; // 代表命令的字符串 -- 命令行上的命令其实就是字符串

    while(true)
    {
        PrintCommandLine();  // 1. 命令行提示符

        if(!GetCommandLine(command_buffer,basesize))    // 2. 获取用户命令
        {
            continue; // 这里代表命令行没有命令
        }

        ParseCommandLine(command_buffer,strlen(command_buffer));  // 3. 分析命令


        // 有些命令是子进程执行
        // 有些命令只能由shell执行 -- 这种命令叫 内建命令 built command 
        // 该函数用于判断命令是否为内建命令 如果是 则直接由该函数调用 无需ExecuteCommand
        if(CheckAndExecBuiltCommand())
        {
            continue;
        }

        ExecuteCommand();
    }

    return 0;
}
